"""
分页
"""

import math

from pydantic import BaseModel

import utils.enum_utils as enum
from models import (
    PageQuery,
    POPage,
)
from my_typing import *

TModel = TypeVar("TModel", bound=BaseModel, covariant=True)
""" 继承 BaseModel 的类（支持协变） """


class Paginator:
    """ 分页器 """

    def __init__(
        self,
        page_query: PageQuery,
        data_list: list[TModel],
    ):
        """
        Args:
            page_query: 分页查询参数
            data_list: 数据模型列表
        """

        self.query: dict = page_query.query
        self.sorting: DictStrInt = page_query.sorting
        self.page: int = page_query.page
        self.page_size: int = page_query.page_size
        self.data_list: list[TModel] = data_list

        self.page_count: int = 0
        self.data_count: int = 0

        self.queried_data_list: list[TModel] = []
        """ 过滤查询条件后的数据列表 """

        self.sorted_data_list: list[TModel] = []
        """ 排序后的数据列表 """

        self.paged_data_list: list[TModel] = []
        """ 分页后的数据列表 """

    def _validate_model_field(self) -> None:
        """ 验证模型字段（查询、排序参数是否在模型的字段中） """

        if not self.data_list:
            return

        model_field_set: SetStr = set(self.data_list[0].model_dump())
        query_field_set: SetStr = set(self.query)
        sorting_field_set: SetStr = set(self.sorting)
        union_field_set: SetStr = query_field_set | sorting_field_set
        """ 查询字段列表 + 排序字段列表 的并集 """

        if not union_field_set:
            return

        if missing_field := union_field_set - model_field_set:
            raise AttributeError(f"查询/排序字段 {missing_field} 不在模型字段中")

    def _need_continue_calc(self) -> bool:
        """
        是否需要继续计算

        不需要计算的情况：
            - 数据条数为 0
            - 分页数据超过了分页数据条数
        """

        return not (
            self.data_list
            and self.page > self.page_count
        )

    @staticmethod
    def _calc_need_append(
        data: TModel,
        query: dict,
    ) -> bool:
        """ 是否需要将数据添加到符合查询条件的数据 """

        op_mapping: DictStrStr = {
            "$eq": "data == op_data",
            "$ne": "data != op_data",
            "$gt": "data > op_data",
            "$gte": "data >= op_data",
            "$lt": "data < op_data",
            "$lte": "data <= op_data",
            "$in": "data in op_data",
            "$nin": "data not in op_data",
            "$regex": "data in op_data",
        }

        for field, value_param in query.items():
            data_value = getattr(data, field, None)

            if not isinstance(value_param, dict):
                if data_value != value_param:
                    return False

                continue

            for op, value in value_param.items():
                op_code: str = op_mapping.get(op) or ""

                if not op_code:
                    raise ValueError(f"不支持的查询操作符 {op}")

                if not eval(op_code, {"data": data_value, "op_data": value}):
                    return False

        return True

    def _query_data(self) -> None:
        """ 查询 """

        if not self.query:
            self.queried_data_list = self.data_list
            return

        for data in self.data_list:
            if not self._calc_need_append(data, self.query):
                continue

            self.queried_data_list.append(data)

    def _count_data(self) -> None:
        """ 计算数据条数 """

        self.data_count = len(self.queried_data_list)
        self.page_count = math.ceil(self.data_count / self.page_size)

    def _sort_data(self) -> None:
        """ 排序 """

        if not self._need_continue_calc():
            return

        if not self.sorting:
            self.sorted_data_list = self.queried_data_list
            return

        sort_reverse: bool = int(enum.MongoSorting.DESC) in self.sorting.values()

        self.sorted_data_list = sorted(
            self.queried_data_list,
            key=lambda x: tuple(
                getattr(x, _field)
                for _field in self.sorting
            ),
            reverse=sort_reverse,
        )

    def _page_data(self) -> None:
        """ 分页 """

        if not self._need_continue_calc():
            return

        start: int = (self.page - 1) * self.page_size
        end: int = start + self.page_size

        self.paged_data_list = self.sorted_data_list[start:end]

    def get_page(self) -> POPage[TModel]:
        """ 获取分页后的数据 """

        self._validate_model_field()
        self._query_data()
        self._count_data()
        self._sort_data()
        self._page_data()

        return POPage(
            page=self.page,
            page_size=self.page_size,
            data_count=self.data_count,
            page_count=self.page_count,
            data_list=self.paged_data_list,
        )
